#include "swpswimlanegraphicitem.h"

#include "../../documents/data/graphicdata.h"
#include "../../undo/basicitemcommands.h"
#include "../../undo/swpitemcommands.h"
#include "../../utils/processdocumentautocomplete.h"
#include "../../utils/undomanager.h"
#include "graphicitemcommon.h"
#include "swpswimlanegraphicitemcontent.h"

#include <QtGui/QGraphicsScene>
#include <QtGui/QGraphicsSceneMouseEvent>

using namespace core;
using namespace gui;
using namespace swp;

SwpSwimlaneGraphicItem::ResizeHandle::ResizeHandle(
    SwpSwimlaneGraphicItem *parent) :
        EllipseGraphicItem(QRectF(0, 0, 25, 25), true, false, parent),
        m_parent(parent),
        m_polygonBeforeChange(),
        m_symbol(0),
        m_ignoreFlag(false),
        m_posInited(false)
{
    QPolygonF polygon;
    polygon << QPointF(5, 12.5) << QPointF(9, 7.5) << QPointF(9, 10.5) <<
        QPointF(16, 10.5) << QPointF(16, 7.5) << QPointF(20, 12.5) <<
               QPointF(16, 17.5) << QPointF(16, 14.5) << QPointF(9, 14.5) <<
               QPointF(9, 17.5) << QPointF(5, 12.5);
    m_symbol = new PolygonGraphicItem(polygon, true, false, this);
    m_symbol->setBrush(Qt::black);

    setFlags(flags() | QGraphicsItem::ItemIsMovable |
        QGraphicsItem::ItemIsSelectable |
        QGraphicsItem::ItemSendsGeometryChanges);
}

void SwpSwimlaneGraphicItem::ResizeHandle::setIgnoreFlag(bool flag)
{
    m_ignoreFlag = flag;
}

void SwpSwimlaneGraphicItem::ResizeHandle::initPos()
{
    m_posInited = true;
}

QVariant SwpSwimlaneGraphicItem::ResizeHandle::itemChange(
    GraphicsItemChange change, const QVariant& value)
{
    QVariant ret;

    if (change == QGraphicsItem::ItemPositionChange ||
        change == QGraphicsItem::ItemPositionHasChanged)
    {
        QPointF _pos = value.toPointF();
        if (m_posInited)
        {
            _pos.ry() = pos().y();

            const qreal pXPos = m_parent->pos().x();
            qreal realX = _pos.x() + 25/2 + pXPos;
            qreal nextXPos = m_parent->m_next != 0 ?
                m_parent->m_next->scenePos().x() : -1;

            if (realX > (m_parent->m_minWidth + pXPos) ||
                qAbs(realX - (m_parent->m_minWidth + pXPos)) < 0.0025)
            {
                if (m_parent->m_next != 0 &&
                        (realX > nextXPos || qAbs(realX - nextXPos) < 0.0025))
                {
                    _pos.rx() = nextXPos - pXPos;

                    ret = QVariant(_pos);

                    _pos.rx() -= 25/2;
                }
                else
                {
                    ret = QVariant(_pos);
                }

                ret = QVariant(_pos);

                if (m_posInited && !m_ignoreFlag)
                {
                    m_parent->resizeWidthTo(_pos.x() + 25/2);
                }
            }
            else
            {
                ret = pos();
            }
        }
        else
        {
            ret = _pos;
        }
    }
    else
    {
        ret = EllipseGraphicItem::itemChange(change, value);
    }

    return ret;
}

void SwpSwimlaneGraphicItem::ResizeHandle::mousePressEvent(
    QGraphicsSceneMouseEvent *event)
{
    m_polygonBeforeChange = m_parent->m_polygon;

    EllipseGraphicItem::mousePressEvent(event);
}

void SwpSwimlaneGraphicItem::ResizeHandle::mouseReleaseEvent(
    QGraphicsSceneMouseEvent *event)
{
    EllipseGraphicItem::mouseReleaseEvent(event);

    if (m_parent->m_polygon != m_polygonBeforeChange)
    {
        const qreal xCoe = m_parent->m_polygon.boundingRect().width() /
            m_polygonBeforeChange.boundingRect().width();
        const qreal yCoe = m_parent->m_polygon.boundingRect().height() /
            m_polygonBeforeChange.boundingRect().height();

        PolygonGraphicItemResizeCommand* command =
            new PolygonGraphicItemResizeCommand(m_parent,
            xCoe, yCoe, true);

        UndoManager::getInstance()->push(m_parent->parentDocumentData(),
            command);
    }
}

SwpSwimlaneGraphicItem::SwitchHandle::SwitchHandle(
    SwpSwimlaneGraphicItem* parent) :
        EllipseGraphicItem(QRectF(0, 0, 25, 25), true, false, parent),
        m_parent(parent),
        m_symbol(0)
{
    QPainterPath path;

    path.moveTo(0, 4);
    path.lineTo(4, 0);
    path.lineTo(4, 2);
    path.quadTo(12, 3, 14, 8);
    path.quadTo(9, 6, 4, 6);
    path.lineTo(4, 8);
    path.lineTo(0, 4);

    QTransform _transform;
    _transform.rotate(180);
    QPainterPath rotatedArrow = _transform.map(path);
    rotatedArrow.translate(21, 21.5);

    path.translate(5, 3.5);
    path = path.united(rotatedArrow);

    m_symbol = new PathGraphicItem(path, true, false, this);
    m_symbol->setBrush(Qt::black);
}

void SwpSwimlaneGraphicItem::SwitchHandle::mousePressEvent(
    QGraphicsSceneMouseEvent *event)
{
    if (m_parent->next() != 0)
    {
        SwapSwimlanesCommand* command = new SwapSwimlanesCommand(m_parent,
            m_parent->next());

        UndoManager::getInstance()->push(m_parent->parentDocumentData(),
            command);

        GraphicData* data = dynamic_cast<GraphicData*>(
            m_parent->parentDocumentData());

        Q_ASSERT(data != 0); // shouldn't be null

        data->emitDataChanged();
    }
}

SwpSwimlaneGraphicItem::SwpSwimlaneGraphicItem(const QString& name,
    QGraphicsItem* parent) : AbstractPolygonGraphicItem(parent),
        m_name(name.isEmpty() ? QObject::tr("swimlane") : name),
        m_nameProp(),
        m_minWidth(SWIMLANE_MIN_WIDTH),
        m_resizeHandle(0),
        m_switchHandle(0),
        m_previous(0),
        m_next(0),
        m_ignoreFlag(false)
{
    m_nameProp.Name = "name";
    m_nameProp.Text = QObject::tr("Swimlane name");
    m_nameProp.setValue(QVariant(m_name));

    QPolygonF polygon;
    polygon << QPointF(0.0, 0.0) << QPointF(300.0, 0.0) <<
        QPointF(300.0, 62.0) << QPointF(0.0, 62.0) << QPointF(0.0, 0.0);

    setFlags(flags() |
        QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);

    setContent(new SwpSwimlaneGraphicItemContent(m_name, this));

    setContentAlignment(Qt::AlignTop | Qt::AlignLeft);

    setZValue(SWIMLANE_ZVALUE);

    setAntialiasing(false);

    m_resizeHandle = new ResizeHandle(this);
    m_resizeHandle->setPos(300 - 25/2,
        m_content->graphicsItem()->boundingRect().height() +
        PREFERRED_CONTENT_ITEM_SPACING);
    m_resizeHandle->initPos();
    m_resizeHandle->setPen(QPen(Qt::black, 2));
    m_resizeHandle->setBrush(Qt::yellow);
    m_resizeHandle->hide();

    m_switchHandle = new SwitchHandle(this);
    m_switchHandle->setPos(300 - 25/2, m_resizeHandle->pos().y() + 25 +
        PREFERRED_CONTENT_ITEM_SPACING);
    m_switchHandle->setPen(QPen(Qt::black, 2));
    m_switchHandle->setBrush(Qt::yellow);
    m_switchHandle->hide();

    setPolygonF(polygon);

    if (scene() != 0)
    {
        GraphicData* _scene = dynamic_cast<GraphicData*>(scene());

        if (_scene != 0)
        {
            _scene->registerForResizeNotification(this);
        }
    }
}

SwpSwimlaneGraphicItem::~SwpSwimlaneGraphicItem()
{
    if (scene() != 0)
    {
        GraphicData* _scene = dynamic_cast<GraphicData*>(scene());

        if (_scene != 0)
        {
            _scene->unregisterForResizeNotification(this);
        }
    }
}

SwpSwimlaneGraphicItem* SwpSwimlaneGraphicItem::previous() const
{
    return m_previous;
}

SwpSwimlaneGraphicItem* SwpSwimlaneGraphicItem::next() const
{
    return m_next;
}

void SwpSwimlaneGraphicItem::setPolygonF(const QPolygonF& polygon)
{
    AbstractPolygonGraphicItem::setPolygonF(polygon);

    m_resizeHandle->setIgnoreFlag(true);
    QPointF _pos = m_resizeHandle->pos();
    _pos.rx() = polygon.boundingRect().width() - 25/2;
    m_resizeHandle->setPos(_pos);
    _pos.ry() = m_switchHandle->pos().y();
    m_switchHandle->setPos(_pos);
    m_resizeHandle->setIgnoreFlag(false);

    if (m_content != 0)
    {
        m_content->recheckIfFits();
    }
}

bool SwpSwimlaneGraphicItem::supportsConnection(
    const ConnectionData& connection) const
{
    bool ret = false;

    return ret;
}

int SwpSwimlaneGraphicItem::type() const
{
    return SWP_SWIMLANE_ITEM;
}

QString SwpSwimlaneGraphicItem::name() const
{
    return QObject::tr("Swimlane");
}

IEditorItem* SwpSwimlaneGraphicItem::copy() const
{
    return new SwpSwimlaneGraphicItem(m_name, 0);
}

QList<Property> SwpSwimlaneGraphicItem::properties() const
{
    QList<Property> ret;

    ret.append(m_nameProp);

    return ret;
}

QVariant SwpSwimlaneGraphicItem::propertyValue(
    const QString& propertyName) const
{
    QVariant ret;

    if (propertyName == "name")
    {
        ret = m_nameProp.value();
    }

    return ret;
}

void SwpSwimlaneGraphicItem::propertyChanged(const QString& property,
    const QVariant& newValue, bool silent)
{
    if (property == m_nameProp.Name &&
        m_nameProp.value() != newValue)
    {
        if (!silent)
        {
            PropertyChangeCommand* command = new PropertyChangeCommand(
                this, m_nameProp, newValue);

            UndoManager::getInstance()->push(
                dynamic_cast<IDocumentData*>(scene()), command);
        }

        m_nameProp.setValue(newValue);
        m_name = newValue.toString();

        SwpSwimlaneGraphicItemContent* _content =
            dynamic_cast<SwpSwimlaneGraphicItemContent*>(content());

        Q_ASSERT(_content != 0); // content mismatch

        _content->setSwimlaneName(m_name);

        if (silent && isSelected()) {
            setSelected(false);
            setSelected(true);
        }

        if (silent)
        {
            GraphicData* data = dynamic_cast<GraphicData*>(m_documentData);

            Q_ASSERT(data != 0); // shouldn't be null

            data->emitDataChanged();
        }
    }
}

QVariantList SwpSwimlaneGraphicItem::optionList(
    const QString& propertyName) const
{
    QVariantList ret;

    return ret;
}

QVariantList SwpSwimlaneGraphicItem::autocomplete(
    const QString& propertyName) const
{
    QVariantList ret;

    if (propertyName == "name")
    {
        ProcessDocumentAutocomplete* ac =
            ProcessDocumentAutocomplete::getInstance();

        const QList<QString> swimlanes = ac->getSwimlanes(m_documentData);

        foreach (const QString& swimlane, swimlanes)
        {
            ret.append(swimlane);
        }
    }

    return ret;
}

QRectF SwpSwimlaneGraphicItem::boundingRectWithoutHandles() const
{
    QRectF ret = m_polygon.boundingRect();

    return ret;
}

void SwpSwimlaneGraphicItem::handleSceneResize()
{
    if (scene() != 0)
    {
        QPolygonF polygon = m_polygon;
        for (int i = 0; i < polygon.size(); ++i)
        {
            if (polygon[i].y() != 0.0)
            {
                QRectF sceneRect = scene()->sceneRect();
                polygon[i].ry() = sceneRect.height() - PREFERRED_PEN_SIZE;
            }
        }

        setPolygonF(polygon);
    }
}

QRectF SwpSwimlaneGraphicItem::boundingRect() const
{
    QRectF ret = AbstractPolygonGraphicItem::boundingRect();

    if (m_resizeHandle != 0 && m_resizeHandle->isVisible())
    {
        const QPointF pos = m_resizeHandle->pos();
        const QRectF rect = m_resizeHandle->boundingRect();
        const QRectF itemRect(pos.x(), pos.y(),
            rect.width(), rect.height());
        ret = ret.united(itemRect);
    }

    if (m_switchHandle != 0 && m_switchHandle->isVisible())
    {
        const QPointF pos = m_switchHandle->pos();
        const QRectF rect = m_switchHandle->boundingRect();
        const QRectF itemRect(pos.x(), pos.y(),
            rect.width(), rect.height());
        ret = ret.united(itemRect);
    }

    return ret;
}

QPainterPath SwpSwimlaneGraphicItem::shape() const
{
    QPainterPath ret = AbstractPolygonGraphicItem::shape();

    if (m_resizeHandle != 0 && m_resizeHandle->isVisible())
    {
        const QPointF pos = m_resizeHandle->pos();
        const QRectF rect = m_resizeHandle->boundingRect();
        const QRectF itemRect(pos.x(), pos.y(),
            rect.width(), rect.height());
        ret.addEllipse(itemRect);
    }

    if (m_switchHandle != 0 && m_switchHandle->isVisible())
    {
        const QPointF pos = m_switchHandle->pos();
        const QRectF rect = m_switchHandle->boundingRect();
        const QRectF itemRect(pos.x(), pos.y(),
            rect.width(), rect.height());
        ret.addEllipse(itemRect);
    }

    return ret;
}

QList<QPointF> SwpSwimlaneGraphicItem::scalePoints() const
{
    QList<QPointF> ret;

    return ret;
}

QList<QPointF> SwpSwimlaneGraphicItem::connectPoints() const
{
    QList<QPointF> ret;

    return ret;
}

QList<QPointF> SwpSwimlaneGraphicItem::selfConnectAnchorPoints() const
{
    QList<QPointF> ret;

    return ret;
}

int SwpSwimlaneGraphicItem::paletteType() const
{
    return Border;
}

QVariant SwpSwimlaneGraphicItem::itemChange(GraphicsItemChange change,
    const QVariant& value)
{
    QVariant ret;
    if (change == QGraphicsItem::ItemSceneHasChanged && scene() != 0)
    {
        ret = value;

        QPolygonF polygon = m_polygon;
        for (int i = 0; i < polygon.size(); ++i)
        {
            if (polygon[i].y() != 0.0)
            {
                QRectF sceneRect = scene()->sceneRect();
                polygon[i].ry() = sceneRect.height() - PREFERRED_PEN_SIZE;
            }
        }

        GraphicData* _scene = dynamic_cast<GraphicData*>(scene());

        if (_scene != 0)
        {
            _scene->registerForResizeNotification(this);
        }

        setPolygonF(polygon);
    }
    else if (change == QGraphicsItem::ItemPositionChange ||
        change == QGraphicsItem::ItemPositionHasChanged)
    {
        ret = AbstractPolygonGraphicItem::itemChange(change, value);

        QPointF _pos = ret.toPointF();
        _pos.ry() = 0;

        if (!m_ignoreFlag)
        {
            const qreal width = boundingRectWithoutHandles().width();

            if (m_next != 0 && (_pos.x() + width) > m_next->pos().x())
            {
                _pos.rx() = m_next->pos().x() - width;
            }

            if (m_previous != 0)
            {
                const qreal pWidth =
                    m_previous->boundingRectWithoutHandles().width();

                if (_pos.x() < (m_previous->pos().x() + pWidth))
                {
                    _pos.rx() = m_previous->pos().x() + pWidth;
                }
            }
        }

        ret = QVariant(_pos);
    }
    else
    {
        ret = AbstractPolygonGraphicItem::itemChange(change, value);
    }

    return ret;
}

void SwpSwimlaneGraphicItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    m_resizeHandle->show();
    m_switchHandle->show();

    AbstractPolygonGraphicItem::hoverEnterEvent(event);
}

void SwpSwimlaneGraphicItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    m_resizeHandle->hide();
    m_switchHandle->hide();

    AbstractPolygonGraphicItem::hoverLeaveEvent(event);
}

void SwpSwimlaneGraphicItem::setPrevious(SwpSwimlaneGraphicItem* previous)
{
    m_previous = previous;
}

void SwpSwimlaneGraphicItem::setNext(SwpSwimlaneGraphicItem* next)
{
    m_next = next;
}

void SwpSwimlaneGraphicItem::setIgnoreFlag(bool flag)
{
    m_ignoreFlag = flag;
}

void SwpSwimlaneGraphicItem::resizeWidthTo(qreal handleX)
{
    qreal currentSceneX = boundingRectWithoutHandles().width();

    qreal coefficient = handleX / currentSceneX;

    if (qAbs(coefficient - 0) > 0.0025)
    {
        GraphicData* data = dynamic_cast<GraphicData*>(m_documentData);

        Q_ASSERT(data != 0); // shouldn't be null

        data->emitDataChanged();
    }

    QTransform transform;
    transform.scale(coefficient, 1);
    QPolygonF transformed = transform.map(m_polygon);

    setPolygonF(transformed);
}
